#!/usr/bin/env python
# encoding: utf-8
"""
Toolkit for working with enumerations from one or more OME XML
(http://www.ome-xml.org) XSD documents.
"""

#
#  Copyright (C) 2009 University of Dundee. All rights reserved.
#
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License along
#  with this program; if not, write to the Free Software Foundation, Inc.,
#  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#


import logging
import sys
import os

# Needed to import modeltools
sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), 'python'))

from ome.modeltools import config
from ome.modeltools import generateds

from configobj import ConfigObj
from getopt import getopt, GetoptError

def usage(error):
	"""
	Prints usage so that we don't have to. :)
	"""
	cmd = sys.argv[0]
	print """%s
Usage: %s (-t | -x | -d <file>) [path/to/ome.xsd]
Toolkit for working with enumerations from an OME XML Schema definition.

Options:
  -t		Dumps all enumerations in a visual tree
  -x		Displays the XPath expressions for each enumeration
  -d file	Diffs enumeration possible values from an XPath configuration
		file. This file is expected to have two sections the first
		containing the "old" and "new" XSD files and the second
		containing the XPath mappings. (See: cfg/example.cfg)
  -n namespace	For -t, -x sets the XSD namespace.

Default namespace: "%s"

Examples:
  %s -t schemas/ome.xsd
  %s -n 'xs:' -x schemas/ome.xsd
  %s -d cfg/example.cfg

Report bugs to OME-Devel <ome-devel@lists.openmicroscopy.org.uk""" % \
	(error, cmd, config.DEFAULT_NAMESPACE, cmd, cmd, cmd)
	sys.exit(2)

def resolve_paths(paths, level, names):
    """
    Resolves a set of ASCII art graphics for a given hierarchical tree
    structure of element names.
    """
    if names is None:
        return
    for name in names.keys():
        path = "+-- %s" % name
        paths.insert(0, path)
        resolve_paths(paths, level + 1, names[name])

def dump(model):
	"""Dumps enumerations to STDOUT."""
	count = 0
	for obj in model.objects.values():
		current = None
		for prop in obj.properties.values():
			if prop.isEnumeration:
				count += 1
				parents = model.resolve_parents(obj.name)
				paths = list()
				resolve_paths(paths, 0, parents)
				for path in paths:
					print path
				print "  +-- %s" % obj.name
				print "    +-- %s(%s)" % (prop.name, prop.type)
				for value in prop.possibleValues:
					print "      +-- %s" % value
	print "\nTotal enumerations: %d" % count

def resolve_xpaths(xpaths, suffix, names):
	"""
	Resolves a set of XPath definitions for a given hierarchical tree
	structure of element names.
	"""
	if names is None:
		return
	for name in names.keys():
		xpath = name
		if suffix is not None:
			xpath = "/".join([xpath, suffix])
		xpaths.append(xpath)
		resolve_xpaths(xpaths, xpath, names[name])
		if suffix in xpaths:
			xpaths.remove(suffix)

def build_xpaths(model):
	"""Builds XPath definitions for each enumeration in the model."""
	xpaths = dict()
	for obj in model.objects.values():
		for prop in obj.properties.values():
			if prop.isEnumeration:
				parents = model.resolve_parents(obj.name)
				parents = {obj.name: parents}
				obj_xpaths = list()
				resolve_xpaths(obj_xpaths, None, parents)
				for xpath in obj_xpaths:
					if prop.isAttribute:
						xpath = "/%s@%s" % (xpath, prop.name)
					else:
						xpath = "/%s/%s" % (xpath, prop.name)
					xpaths[xpath] = {'object': obj, 'property': prop}
	return xpaths

def dump_xpath(model):
	"""
	Dumps XPath definitions for each enumeration to STDOUT, sorted
	hierarchically.
	"""
	for xpath in build_xpaths(model).keys():
		print xpath

def diff_enumerations(old, old_ns, new, new_ns, xpaths):
	"""Diffs all enumerations from two XSD files."""
	old_model = generateds.parse(old, old_ns)
	old_xpaths = build_xpaths(old_model)
	generateds.reset()
	new_model = generateds.parse(new, new_ns)
	new_xpaths = build_xpaths(new_model)
	for old_xpath, new_xpath in xpaths.items():
		old_values = old_xpaths[old_xpath]['property'].possibleValues
		new_values = new_xpaths[new_xpath]['property'].possibleValues
		for old_value in old_values:
			if old_value not in new_values:
				print "%s:%s not in %s" % (old_xpath, old_value, new_xpath)

if __name__ == '__main__':
	try:
		options, args = getopt(sys.argv[1:], "txd:n:")
	except GetoptError, (msg, opt):
		usage(msg)

	namespace = config.DEFAULT_NAMESPACE
	do_tree = False
	do_xpath = False
	config = None
	for option, argument in options:
		if option == "-t":
			do_tree = True
		if option == "-x":
			do_xpath = True
		if option == "-d":
			config = argument
		if option == "-n":
			namespace = argument

	if config is not None:
		config = ConfigObj(config)
		old = config['schemas']['old']
		try:
			old_ns = config['schemas']['oldNS']
		except KeyError:
			old_ns = config.DEFAULT_NAMESPACE
		new = config['schemas']['new']
		try:
			new_ns = config['schemas']['newNS']
		except KeyError:
			new_ns = config.DEFAULT_NAMESPACE
		xpaths = config['xpaths']
		diff_enumerations(old, old_ns, new, new_ns, xpaths)
		sys.exit(0)

	if len(args) != 1:
		usage("Expecting single ome.xsd file path!")
	elif not do_tree and not do_xpath:
		usage("Must select at least one option!")

	model = generateds.parse(args[0], namespace)
	if do_tree:
		dump(model)
	if do_xpath:
		dump_xpath(model)
	sys.exit(0)
